Skip to content

feat(Init): Initial upload of array + interoperability package#1

Open
Simpag wants to merge 10 commits into
team-decent:mainfrom
Simpag:dev1
Open

feat(Init): Initial upload of array + interoperability package#1
Simpag wants to merge 10 commits into
team-decent:mainfrom
Simpag:dev1

Conversation

@Simpag
Copy link
Copy Markdown
Collaborator

@Simpag Simpag commented May 10, 2026

Initial upload of array + interoperability modules, including tests, docs, type-checking and compilation. Better docs and a README is needed.

Copilot AI review requested due to automatic review settings May 10, 2026 18:22
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Initial import of the decent_array package, providing an Array wrapper and a single-active-backend interoperability layer with NumPy/PyTorch/JAX/TensorFlow backends, plus CI, docs, benchmarks, and a comprehensive pytest suite.

Changes:

  • Added backend manager + abstract backend contract, plus concrete backends (NumPy/PyTorch/JAX/TensorFlow) and module-level iop functions/RNG coordination.
  • Added Array wrapper implementing operators, indexing, and common properties via the active backend.
  • Added tox/CI/Sphinx/ReadTheDocs scaffolding, benchmarks, and extensive tests.

Reviewed changes

Copilot reviewed 49 out of 52 changed files in this pull request and generated 12 comments.

Show a summary per file
File Description
tests/test_iop_rng.py Tests for RNG seeding, snapshot/restore, and distribution helpers.
tests/test_iop_functions.py Tests for module-level interoperability functions (creation, math, linalg, indexing).
tests/test_decorators.py Tests for the cost-method autodecorator argument unwrapping/rewrapping behavior.
tests/test_backend_manager.py Tests backend activation, registration, listener behavior, and reset semantics.
tests/test_array.py Tests Array operators/dunders, indexing, coercion, and properties across backends.
tests/conftest.py Parametrized backend/device fixture with skip logic for unavailable combinations.
readthedocs.yaml Read the Docs build configuration.
pyproject.toml Project metadata, dependencies/extras, tox envs, ruff/mypy/pytest config, mypyc build hook.
LICENSE Adds AGPL-3.0 license text.
docs/sphinx_theme.txt Sphinx theme requirements list.
docs/source/user.rst Initial user guide stub + installation snippet.
docs/source/index.rst Sphinx landing page and toctree.
docs/source/developer.rst Developer guide covering tox workflows, mypyc usage, and contribution process.
docs/source/conf.py Sphinx configuration (theme, intersphinx, missing-reference fixups, sidebar config).
docs/source/background.rst Background page stub.
docs/source/author.rst Contributors page.
docs/source/api/decent_array.types.rst API stub for decent_array.types.
docs/source/api/decent_array.rst API toctree root.
docs/source/api/decent_array.interoperability.rst API stub for decent_array.interoperability.
docs/source/api/decent_array.array.rst API stub for decent_array (exports Array).
docs/source/_templates/package.rst.jinja Custom sphinx-apidoc package template.
docs/source/_templates/module.rst.jinja Custom sphinx-apidoc module template.
docs/source/_static/custom.css Small theme CSS tweak for logo sizing.
docs/Makefile Sphinx Makefile (unix).
docs/make.bat Sphinx make script (windows).
decent_array/types.py Shared type aliases + supported framework/device enums.
decent_array/interoperability/_tensorflow/tensorflow_backend.py TensorFlow backend implementation + registration side-effect.
decent_array/interoperability/_tensorflow/init.py TensorFlow backend package initializer/export.
decent_array/interoperability/_pytorch/pytorch_backend.py PyTorch backend implementation + registration side-effect.
decent_array/interoperability/_pytorch/init.py PyTorch backend package initializer/export.
decent_array/interoperability/_numpy/numpy_backend.py NumPy backend implementation + registration side-effect.
decent_array/interoperability/_numpy/init.py NumPy backend package initializer/export.
decent_array/interoperability/_jax/jax_backend.py JAX backend implementation + registration side-effect.
decent_array/interoperability/_jax/init.py JAX backend package initializer/export.
decent_array/interoperability/_iop/rng.py Cross-backend RNG coordination (python random, numpy, backend) + distributions.
decent_array/interoperability/_iop/functions.py Module-level iop function surface delegating to the active backend.
decent_array/interoperability/_iop/init.py iop package marker/init (empty).
decent_array/interoperability/_decorators.py Decorator to unwrap Array args for backend-native cost-method implementations.
decent_array/interoperability/_backend_manager.py Backend registry/activation/reset + auto-import and listener mechanism.
decent_array/interoperability/_abstracts/backend.py Abstract Backend API contract (creation/manipulation/linalg/math/RNG).
decent_array/interoperability/_abstracts/init.py Abstracts package export (Backend).
decent_array/interoperability/init.py Public interoperability API exports (functions + RNG helpers + decorator).
decent_array/_array.py Array wrapper implementation (operators, indexing, properties) tied to active backend.
decent_array/init.py Package exports (Array, interoperability, types).
benchmarks/profile_hotpath.py cProfile-based hot-path profiler for wrapper vs function dispatch.
benchmarks/bench_iop.py Microbenchmark for iop function-call overhead vs native frameworks.
benchmarks/bench_common.py Shared benchmark helpers (backend discovery/activation + timing utilities).
benchmarks/bench_array.py Microbenchmark for Array operator overhead vs native frameworks.
.gitignore Python/build/dev artifact ignores.
.github/workflows/ci.yaml CI pipeline running tox envs (mypy/pytest/ruff/sphinx/mypyc) on Py3.13 across OSes.
.github/CODEOWNERS Sets default code owners for the repo.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread benchmarks/bench_common.py Outdated
Comment thread benchmarks/bench_common.py Outdated
Comment thread benchmarks/bench_iop.py Outdated
Comment thread decent_array/interoperability/_iop/functions.py Outdated
Comment thread decent_array/interoperability/_backend_manager.py Outdated
Comment thread docs/source/author.rst Outdated
Comment thread tests/test_iop_rng.py
Comment thread pyproject.toml
Comment on lines +1 to +16
[project]
name = "decent-array"
version = "0.1.0"
authors = [{name = "Elias Ram"}, {name = "Simon Granström"}, {name = "Adriana Rodriguez"}, {name = "Nicola Bastianello"}]
maintainers = [{name = "Team Decent"}]
description = "A library of array operations and linear algebra primitives for interoperability across ML frameworks."
readme = "README.md"
requires-python = ">=3.13"
classifiers = [
"Programming Language :: Python :: 3.13",
"Operating System :: OS Independent",
]
license = "AGPL-3.0-only"
dependencies = [
"numpy",
]
Comment thread docs/source/developer.rst
Comment thread decent_array/_array.py Outdated
@Simpag
Copy link
Copy Markdown
Collaborator Author

Simpag commented May 10, 2026

I will try to remember to upload benchmark results tomorrow evening

@Simpag Simpag requested a review from nicola-bastianello May 10, 2026 21:33
Copy link
Copy Markdown
Member

@nicola-bastianello nicola-bastianello left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

first batch of comments, mostly on docstrings and docs. a few of the comments on adding stuff to docs we can copy-paste to a new issue and address them later

Comment thread benchmarks/bench_array.py
@@ -0,0 +1,71 @@
"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be nice to add instructions on how to run the benchmarks and interpret the results; maybe we add this when preparing the docs

"""
Abstract :class:`Backend` contract.

All abstract methods live in this single class rather than across six mixin ABCs. The
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this intro can be edited to remove mention of mixin, and the rest of the discussion should be included in/moved to the docs

@@ -0,0 +1,301 @@
"""
JAX backend for interoperability_2.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

some mentions of interoperability_2 still present

package can be auto-loaded on the first ``set_backend("tensorflow")`` call.

TF eager Tensors are immutable, so :meth:`set_item` round-trips through numpy and the
in-place math operations rebind the wrapper's underlying value.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a discussion on this should be included in the docs; let's open an issue about docs to keep track of what we should include


Args:
backend: A :class:`~decent_array.types.SupportedFrameworks` value, its canonical string (e.g.
``"numpy"``, ``"pytorch"``), or any alias declared by the backend at
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mention of alias should be removed

decent\_array
=============

.. toctree::
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change the order: array, interoperability, types

Comment thread docs/source/author.rst
============

decent-array is developed by `Simon Granström <https://github.com/Simpag/>`_ and
`Adriana Rodriguez <https://github.com/adrianardv/>`_, under the supervision of
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if Adriana has not directly contributed, we can remove her name for now. here I'd keep only people who have committed

Comment thread docs/source/developer.rst



Compiled hot path (mypyc)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

when running tox -e mypyc for the first time I got error: Microsoft Visual C++ 14.0 or greater is required. Get it with "Microsoft C++ Build Tools": https://visualstudio.microsoft.com/visual-cpp-build-tools/

I think here we should mention this possibility, and how to solve it for windows, I don't know about linux/max: 1. go to that website, 2. click download build tools, run the .exe, 3. select "C++ build tools" and install. this is going to take a while and install >6GB of stuff (not ideal, but it looks like there's no way around this storage footprint)

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, you need a C compiler to make this work. Can add it to the docs

Comment thread docs/source/user.rst
@@ -0,0 +1,12 @@
User Guide
==========
This user guide shows you different examples of how to use decent-array.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

sentence can be removed for now maybe? and we add to the docs todo list adding examples here, but I'd say it's lower priority

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All docs are pure boilerplate. I copied them from decent-bench

@@ -0,0 +1,7 @@
decent\_array.interoperability
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably find a way to divide the interop api in different pages, each collecting a category of functions (math, linalg, rng, etc). we can discuss the best division

Copy link
Copy Markdown
Member

@nicola-bastianello nicola-bastianello left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

second batch of comments on repo set-up

Comment thread .gitignore
@@ -0,0 +1,11 @@
**/__pycache__
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe we should add .pyd?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

on my system at least they are not ignored by vscode source control

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not get those on linux, I'll add more python metadata to the ignore list

Comment thread pyproject.toml
[project]
name = "decent-array"
version = "0.1.0"
authors = [{name = "Elias Ram"}, {name = "Simon Granström"}, {name = "Adriana Rodriguez"}, {name = "Nicola Bastianello"}]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to be changed to you and me only

Comment thread pyproject.toml
Issues = "https://github.com/team-decent/decent-array/issues"

[project.optional-dependencies]
dev = [
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure about the division of optional dependencies here; maybe we should have separate items for torch, tensorflow and jax? or also put torch in dev-cpu and dev-gpu instead of in dev (to be "democratic")

also, "scipy-stubs", "types-networkx", "types-tabulate", might not be needed

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the dev, dev-cpu and dev-gpu are interconnected. A dev environment will have dev + dev-cpu or dev + dev-gpu. The split between dev-cpu and dev-gpu is only performed because they require different sub-packages to be installed properly. Torch can install on cpu only devices and gpu devices using the same method. We could move torch to dev-cpu but we'd have to force torch to only install cpu components.

Comment thread pyproject.toml
mypy-args = ["--ignore-missing-imports"]

[tool.tox]
envlist = ["dev", "mypy", "pytest", "ruff", "sphinx"]
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

during my own dev work, I've found that I like having time-consuming checks later; maybe we could change this order to: dev, ruff, mypy, pytest, sphinx? this way if ruff and mypy fail early, one can cancel execution of the checks, instead of waiting for pytest to finish before seeing if ruff is failing

of course one can argue about priorities, that pytest is more important.. let's discuss

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know that one can run the checks individually, but sometimes I'm overly optimistic and run tox directly thinking all checks will be green

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The full tox command should generally only be performed right before commit, we might want to add a lint environment that calls ruff + mypy

Copy link
Copy Markdown
Member

@nicola-bastianello nicola-bastianello left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

third batch of comments, this time on aligning to the array api

some of the signatures proposed by array api are a bit more complicated than what we currently have. so for now we could just make small changes (like introducing / where needed), and leave bigger changes on the type annotations for later

Comment thread decent_array/_array.py
return self.transpose

@property
def any(self) -> bool:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all and any are not properties in the array api, I would remove them. transpose should be exposed only as T

https://data-apis.org/array-api/latest/API_specification/array_object.html#attributes

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All and any are properties of numpy, torch and jax. I tend to use them a lot because its convenient. Only tensorflow does not have it because of its unique way to handle arrays.

All these frameworks also expose T + transpose. While I don’t care mind removing it, I tend to use .transpose for readability over .T

Comment thread decent_array/_array.py
"""Return the sum of the array and another array or a scalar."""
return Array(self.value + (other.value if type(other) is Array else other))

def __radd__(self, other: float) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as per array api: The results of applying reflected operators must match their non-reflected equivalents. https://data-apis.org/array-api/latest/API_specification/array_object.html#reflected-operators

this means that type of other should be expanded to include Array

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Its not needed because if we add two arrays the __add__ will be called over __radd__. The other argument will never be of type Array in __radd__ so I'm not sure what they are talking about, might be poor wording on their end

Comment thread decent_array/_array.py
"""Return the sum of the array and a scalar."""
return Array(other + self.value)

def __sub__(self, other: Array | float) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to PEP 484, float encapsulates int so int | float is redundant (our linting also highlights this). We could discuss if we want to support complex numbers or not. I believe that it is safe so we could switch, then complex would include int and float so no need to explicitly type this.

Comment thread decent_array/_array.py
"""Return the subtraction of the array from a scalar."""
return Array(other - self.value)

def __mul__(self, other: Array | float) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread decent_array/_array.py
"""Return the product of the array and a scalar."""
return Array(other * self.value)

def __truediv__(self, other: Array | float) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread decent_array/_array.py
"""Element-wise greater-than."""
return Array(self.value > (other.value if type(other) is Array else other))

def __ge__(self, other: Array | float) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread decent_array/_array.py
# which fails TF's ``1 & bool_tensor`` rejection. Native operator semantics on
# the wrapped tensor enforce the actual dtype contract.

def __and__(self, other: Array | int) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment thread decent_array/_array.py

# Indexing -------------------------------------------------------------

def __getitem__(self, key: ArrayKey) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array.__getitem__(key: int | slice | ellipsis | None | Tuple[int | slice | ellipsis | array | None, ...] | array, /) -> array

https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__getitem__.html#array_api.array.__getitem__

the signature is a bit more complex than what we currently have, let's discuss if we want to expand it

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've tried more complex key types but it very easily collapses or doesn’t compile

Comment thread decent_array/_array.py
"""Return the item at ``key``."""
return self._backend.get_item(self, key)

def __setitem__(self, key: ArrayKey, value: Array | float) -> None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array.__setitem__(key: int | slice | ellipsis | Tuple[int | slice | ellipsis | array, ...] | array, value: int | float | complex | bool | array, /) -> None

https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__setitem__.html#array_api.array.__setitem__

Comment thread decent_array/_array.py

# Containers / iteration ----------------------------------------------

def __len__(self) -> int:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

__len__ is not in the array api, and I couldn't find an explanation. since all backeneds allow for this operation, let's keep it

@nicola-bastianello nicola-bastianello mentioned this pull request May 14, 2026
Copy link
Copy Markdown
Member

@nicola-bastianello nicola-bastianello left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

other comments, on private vs. public

listener(_BACKEND_INSTANCE)


def register_backend_listener(listener: Callable[[Backend | None], None]) -> None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

register_backend_listener maybe should be private? I'm not sure I see a situation where a user would need this function

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These are not exposed to the public api but are used within the codebase which is why they are not explicitly "private". Generally private methods are only used within the same script, not cross modules

_COORDINATOR.set_rng_state(state)


def derive_seed() -> int:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

derive_seed should maybe be private? I think it would just be confusing for users.

but if we decide to keep it public, then the docs should explain in some detail how the seed is derived

Copy link
Copy Markdown
Collaborator Author

@Simpag Simpag May 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

derive seed is needed for users to create their own rng system in a "deterministic random" way when a seed is set using iop.set_seed. For example in cost functions, datasets or schemes

def __init__(self) -> None:
self._global_seed: int | None = None

def set_seed(self, seed: int, *, set_global_seed: bool = True) -> None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_global_seed is an option that might be confusing to users. we could consider spinning off a _set_local_seed function and remove the option from set_seed

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No methods in the _RngCoordinator are exposed. See set_seed futher down

Comment thread benchmarks/bench_array.py
Run with::

python benchmarks/bench_array.py
"""
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it would be nice to have benchmarks that directly compare compiled and uncompiled versions; or at least have an option at the top to say which version should be used

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in any case all the benchmarks look good to me. I run them and the results of compilation are fantastic!

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The benchmarks print if they are compiled or not. I dont think there's an easy way to compare them in the same script. Could possibly do it using sub-processes but it would be rather intrusive because we'd need to delete compile files

Copy link
Copy Markdown
Member

@nicola-bastianello nicola-bastianello left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments on the iop api

my priority for now is to ensure compatibility with the array api. this means changing the names and signatures of many functions, but at this stage we don't need to change the behavior (e.g. we can ignore arguments that are not currently supported). my idea is that aligning the api right now is easier than doing it later, and that changing behavior is easier to do at a later time

# Array creation


def zeros(shape: tuple[int, ...]) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

array api suggests the following signature: zeros(shape: int | Tuple[int, ...], *, dtype: dtype | None = None, device: device | None = None) -> array https://data-apis.org/array-api/latest/API_specification/generated/array_api.zeros.html#array_api.zeros

they make device a kwarg; in the current setup, however, device choice is part of the backend. some options: 1) we avoid an explicit device kwarg, 2) we introduce it with default None and always ignore it (like numpy does), 3) we implement creating/moving arrays between different devices. I'm leaning towards 2), which would allow us to go for 3) in the future if we think it's interesting

in any case, I think we should type as int | Tuple[int, ...] like they do and expose dtype if possible

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same discussion applies to zeros_like, ones, ones_like, eye, etc..

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I dont see a reason to expose the device param if we want to keep it simple. It could easily cause device conflicts and users would have to think about device management

return _BACKEND_INSTANCE.zeros(shape)


def zeros_like(array: Array) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

zeros_like(x: array, /, *, dtype: dtype | None = None, device: device | None = None) -> array

https://data-apis.org/array-api/latest/API_specification/generated/array_api.zeros_like.html#array_api.zeros_like

return _BACKEND_INSTANCE.zeros_like(array)


def ones(shape: tuple[int, ...]) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ones(shape: int | Tuple[int, ...], *, dtype: dtype | None = None, device: device | None = None) -> array

https://data-apis.org/array-api/latest/API_specification/generated/array_api.ones.html#array_api.ones

return _BACKEND_INSTANCE.ones(shape)


def ones_like(array: Array) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ones_like(x: array, /, *, dtype: dtype | None = None, device: device | None = None) → array

https://data-apis.org/array-api/latest/API_specification/generated/array_api.ones_like.html#array_api.ones_like

return _BACKEND_INSTANCE.ones_like(array)


def eye(n: int) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

eye(n_rows: int, n_cols: int | None = None, /, *, k: int = 0, dtype: dtype | None = None, device: device | None = None) -> array

https://data-apis.org/array-api/latest/API_specification/generated/array_api.eye.html#array_api.eye

return _BACKEND_INSTANCE.sign(array)


def maximum(array1: Array | float, array2: Array | float) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maximum(x1: array | int | float, x2: array | int | float, /) -> array https://data-apis.org/array-api/latest/API_specification/generated/array_api.maximum.html#array_api.maximum

return _BACKEND_INSTANCE.maximum(array1, array2)


def argmax(array: Array, axis: int | None = None, keepdims: bool = False) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argmax(x: array, /, *, axis: int | None = None, keepdims: bool = False) -> array https://data-apis.org/array-api/latest/API_specification/generated/array_api.argmax.html#array_api.argmax

return _BACKEND_INSTANCE.argmax(array, axis, keepdims)


def argmin(array: Array, axis: int | None = None, keepdims: bool = False) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

argmin(x: array, /, *, axis: int | None = None, keepdims: bool = False) -> array https://data-apis.org/array-api/latest/API_specification/generated/array_api.argmin.html#array_api.argmin

return _BACKEND_INSTANCE.argmin(array, axis, keepdims)


def set_item(array: Array, key: ArrayKey, value: Array) -> None:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

set_item and get_item could also be private, since they are called by the corresponding dunder methods

the signature should also align with the api as much as possible, e.g. array arg to x and the other args align to

array.__setitem__(key: int | slice | ellipsis | Tuple[int | slice | ellipsis | array, ...] | array, value: int | float | complex | bool | array, /) -> None https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__setitem__.html#array_api.array.__setitem__

array.__getitem__(key: int | slice | ellipsis | None | Tuple[int | slice | ellipsis | array | None, ...] | array, /) -> array https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.__getitem__.html#array_api.array.__getitem__

return (current_seed + random_data) % (2**32)


def normal(mean: float = 0.0, std: float = 1.0, shape: tuple[int, ...] = ()) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

random functions for array creation are not currently covered by the array api, so let's make our best guess at a good api, knowing that in the future we might need to break it if we want compatibility

normal, uniform, choice look good; but normal_like and uniform_like are not universally exposed. I would remove the two *_like functions, since it's not difficult to grab the shape from an array. if we decide to keep them, array arg should be renamed x

we could also include the dtype arg that e.g. torch, jax, tf expose

Copy link
Copy Markdown
Member

@nicola-bastianello nicola-bastianello left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments on conversion to/from numpy, and on devices

return _BACKEND_INSTANCE.device_to_native(device)


def device_of(array: Array) -> SupportedDevices:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the array api suggests exposing device as a property of array objects. I think we should do the same, and make this function into a private utility. (as context: pytorch and tf also expose .device, in jax one needs .devices())

https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.device.html

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the array api also suggests exposing:

I think we could already add default_device to return the backend device; devices we can discuss later

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

another thing to discuss later is array.to_device(device: device, /, *, stream: int | Any | None = None) -> array https://data-apis.org/array-api/latest/API_specification/generated/array_api.array.to_device.html#array_api.array.to_device

this is part of the discussion of allowing device as an arg in array creation functions

return _BACKEND_INSTANCE.eye_like(array)


def device_to_native(device: SupportedDevices) -> Any: # noqa: ANN401
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should probably be private

return _BACKEND_INSTANCE.copy(array)


def to_numpy(array: Array) -> NDArray[Any]:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

both torch and tf use tensor.numpy() for conversion to numpy. we could change this function to private and call it from Array.numpy(). jax is a bit different but I think it's fair to use .numpy() as the api choice

related #3, which would require implementing __array__ and __array_wrap__, but that's a discussion for later

return _BACKEND_INSTANCE.to_numpy(array)


def from_numpy(array: NDArray[Any]) -> Array:
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

all frameworks seem to do this differently, so let's keep the name from_numpy. only change: array arg to x as in the rest of the api

@nicola-bastianello
Copy link
Copy Markdown
Member

another comment: I like that with the new version there is no need for _NoBackend class that replicates the same api but always raises. however, having a is None check in every iop function might add a slight overhead (probably very little). so I've thought of an alternative: we go back to a custom class, and use __getattribute__ to raise with the custom error message

class _NoBackend:
    def __getattribute__(self, name):
        raise RuntimeError(...)

this would allow removing all checks from the iop functions. again, I'm not sure this is much of an improvement in terms of performance, but it would at least cut out a lot of repeated code

@Simpag
Copy link
Copy Markdown
Collaborator Author

Simpag commented May 16, 2026

another comment: I like that with the new version there is no need for _NoBackend class that replicates the same api but always raises. however, having a is None check in every iop function might add a slight overhead (probably very little). so I've thought of an alternative: we go back to a custom class, and use __getattribute__ to raise with the custom error message

class _NoBackend:
    def __getattribute__(self, name):
        raise RuntimeError(...)

this would allow removing all checks from the iop functions. again, I'm not sure this is much of an improvement in terms of performance, but it would at least cut out a lot of repeated code

I've already tried this and it doesnt compile. _NoBackend would not be of type Backend so it is not assignable to backend instance. So we'd need to implement every abstract method. The current solution was the best I could come up with without having some very complex code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants